import {
  Injectable,
  ComponentRef,
  Renderer2,
  RendererFactory2,
  ComponentFactoryResolver,
  ApplicationRef,
  OnDestroy,
  Injector,
  TemplateRef,
} from '@angular/core';
import { ComponentPortal, DomPortalOutlet } from '@angular/cdk/portal';
import { Overlay } from '@angular/cdk/overlay';
import { LvConfig } from '../core/config';
import { NotificationContainerComponent } from './notification.container.component';
import { NotificationOptions } from './interface';
import { NotificationContainerService } from './notification.container.service';

const containerMap = new Map<string, NotificationContainerComponent>();
const NOTIFICATION_DEFAULT_OPTIONS = {
  lvDuration: 0,
  lvKeepShowOnHover: true,
  lvPosition: 'bottomRight',
  lvOffset: { x: 0, y: 0 },
};

@Injectable({ providedIn: 'root' })
export class NotificationService implements OnDestroy {
  private renderer: Renderer2;

  constructor(
    private overlay: Overlay,
    rendererFactory: RendererFactory2,
    private cfr: ComponentFactoryResolver,
    private appRef: ApplicationRef,
  ) {
    this.renderer = rendererFactory.createRenderer(null, null);
  }

  ngOnDestroy(): void {
    containerMap.clear();
  }

  create(options: NotificationOptions): void {
    this._createNotification(options);
  }

  destroy(id?): void {
    const _id = id ? id : null;
    const names = [
      'topLeftNotificationContainer',
      'topRightNotificationContainer',
      'bottomLeftNotificationContainer',
      'bottomRightNotificationContainer',
    ];
    names.forEach((item): void => {
      this._removeNotification(item, _id);
    });
  }

  private _generateId(): string {
    const prefix = 'notification-';
    const suffix = new Date().getTime();
    return prefix + suffix;
  }

  private _removeNotification(containerName, id): void {
    const container = containerMap.get(containerName) as NotificationContainerComponent;

    if (container === undefined) {
      return;
    }

    if (id) {
      container.remove(id);
    } else {
      container.removeAll();
    }
  }

  private _mergeOptions(options: NotificationOptions): {
    lvPosition: string | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
    lvTitle?: string;
    lvOnDestroy?: () => void | object;
    lvWidth?: number;
    lvContent?: string | TemplateRef<void>;
    lvOffset: { x: 0; y: 0 } | { x?: number; y?: number } | { x: number; y: number };
    lvNotificationKey?: string;
    lvZIndex?: number;
    lvKeepShowOnHover: boolean;
    lvDuration: number;
    lvMaxStacks?: number;
    lvIcon?: string;
  } {
    const globalOptions = LvConfig.notificationOptions;
    return { ...NOTIFICATION_DEFAULT_OPTIONS, ...globalOptions, ...options };
  }

  private _createNotification(options): {
    options: {
      lvPosition: string | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
      lvTitle?: string;
      lvOnDestroy?: () => void | object;
      lvWidth?: number;
      lvContent?: string | TemplateRef<void>;
      lvOffset: { x?: number; y?: number } | { x: number; y: number };
      lvNotificationKey?: string;
      lvZIndex?: number;
      lvKeepShowOnHover: boolean;
      lvDuration: number;
      lvMaxStacks?: number;
      lvIcon?: string;
    };
    id: string;
  } {
    const result = {
      id: options.lvNotificationKey || this._generateId(),
      options: this._mergeOptions(options),
    };
    const containerName = `${result.options.lvPosition}NotificationContainer`;
    const container = this._createContainer(containerName, options);

    container.create(result);
    return result;
  }

  private _createContainer(name: string, options: NotificationOptions): NotificationContainerComponent {
    if (containerMap.has(name)) {
      return containerMap.get(name) as NotificationContainerComponent;
    }

    let containerRef: ComponentRef<NotificationContainerComponent> | null;
    // Create a notification container
    const positionMap = {
      topLeftNotificationContainer: 'lv-notification-container-topLeft',
      topRightNotificationContainer: 'lv-notification-container-topRight',
      bottomLeftNotificationContainer: 'lv-notification-container-bottomLeft',
      bottomRightNotificationContainer: 'lv-notification-container-bottomRight',
    };
    const containerEle = document.querySelector(`.${positionMap[name]}`);
    const injector = Injector.create({
      providers: [{ provide: NotificationContainerService, useClass: NotificationContainerService }],
    });
    const portal = new ComponentPortal(NotificationContainerComponent, undefined, injector);
    if (containerEle) {
      // 多个<lv-notification-container>挂载在一个元素上
      const portalHost = new DomPortalOutlet(containerEle, this.cfr, this.appRef);
      containerRef = portalHost.attach(portal);
    } else {
      const overlayRef = this.overlay.create();

      containerRef = overlayRef.attach(portal);
      containerRef.changeDetectorRef.detectChanges(); // Manual change detection to avoid multiple detection errors

      // Set notification container position
      this.renderer.addClass(overlayRef.overlayElement, 'lv-notification-container');

      this.renderer.addClass(overlayRef.overlayElement, positionMap[name]);

      if (options.lvZIndex) {
        this.renderer.setStyle(overlayRef.overlayElement, 'z-index', options.lvZIndex);
      }
    }

    // Save container map
    containerMap.set(name, containerRef.instance);

    return containerRef.instance;
  }
}
